Skip to content

Release v1.2.0#1472

Merged
jeffhandley merged 1 commit intomainfrom
release-1.2.0
Mar 27, 2026
Merged

Release v1.2.0#1472
jeffhandley merged 1 commit intomainfrom
release-1.2.0

Conversation

@jeffhandley
Copy link
Copy Markdown
Contributor

@jeffhandley jeffhandley commented Mar 27, 2026

Release v1.2.0

This release improves stateless HTTP transport defaults and documentation with a breaking behavioral change that we are considering as a server reliability fix and therefore not bumping the major version with this release. Legacy SSE endpoints are now disabled by default with a new HttpServerTransportOptions.EnableLegacySse property available to opt back into responding to the SSE endpoints; the property is marked as an [Obsolete] warning as we expect to remove this property in a future major version.

A warning-level [Obsolete] attribute is also applied to the RequestContext<TParams>(McpServer, JsonRpcRequest) constructor, and the RequestContext<TParams>(McpServer, JsonRpcRequest, TParams) overload should be used instead. This change contributes to fixes including DI scope lifetime in task-augmented tools, meta/progress combination failures, and outgoing message filter routing. We plan to remove the obsolete overload in a future major version.

Breaking Changes

Refer to the C# SDK Versioning documentation for details on versioning and breaking change policies.

1. Disable legacy SSE by default #1468

MapMcp() no longer maps /sse and /message endpoints by default. Servers whose clients connect via SSE will find those endpoints removed.

Migrating from legacy SSE

If your clients connect to a /sse endpoint (e.g., https://my-server.example.com/sse), they were using the legacy SSE transport--if not running in Stateless mode. The /sse and /message endpoints are now disabled by default (xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.EnableLegacySse is false and marked [Obsolete] with diagnostic MCP9003). Upgrading the server SDK without updating clients will break SSE connections.

Client-side migration. Change the client Endpoint from the /sse path to the root MCP endpoint — the same URL your server passes to MapMcp(). For example:

// Before (legacy SSE):
Endpoint = new Uri("https://my-server.example.com/sse")

// After (Streamable HTTP):
Endpoint = new Uri("https://my-server.example.com/")

With the default HttpTransportMode.AutoDetect transport mode, the client automatically tries Streamable HTTP first. You can also set TransportMode = HttpTransportMode.StreamableHttp explicitly if you know the server supports it.

Server-side migration. If you previously relied on /sse being mapped automatically, you now need EnableLegacySse = true (suppressing the MCP9003 warning) to keep serving those endpoints. The recommended path is to migrate all clients to Streamable HTTP and then remove EnableLegacySse.

Transition period. If some clients still need SSE while others have already migrated to Streamable HTTP, set EnableLegacySse = true with Stateless = false. Both transports are served simultaneously by MapMcp() — Streamable HTTP on the root endpoint and SSE on /sse and /message. Once all clients have migrated, remove EnableLegacySse and optionally switch to Stateless = true.

SSE (legacy — opt-in only)

Legacy SSE endpoints are now disabled by default and must be explicitly enabled via HttpServerTransportOptions.EnableLegacySse. This is the primary reason they are disabled — the SSE transport has no built-in HTTP-level backpressure.

The legacy SSE transport separates the request and response channels: clients POST JSON-RPC messages to /message and receive responses through a long-lived GET SSE stream on /sse. The POST endpoint returns 202 Accepted immediately after queuing the message — it does not wait for the handler to complete. This means there is no HTTP-level backpressure on handler concurrency, because each POST frees its connection immediately regardless of how long the handler runs.

Internally, handlers are dispatched with a fire-and-forget pattern. A client can send unlimited POST requests to /message while keeping the GET stream open, and each one spawns a concurrent handler with no built-in limit.

The GET stream does provide session lifetime bounds: handler cancellation tokens are linked to the GET request's HttpContext.RequestAborted, so when the client disconnects the SSE stream, all in-flight handlers are cancelled. This is similar to SignalR's connection-bound lifetime model — but unlike SignalR, there is no per-client concurrency limit like MaximumParallelInvocationsPerClient. The GET stream provides cleanup on disconnect, not rate-limiting during the connection.

2. Obsolete 2-arg RequestContext constructor #1462

The RequestContext<TParams>(McpServer, JsonRpcRequest) constructor is now [Obsolete] with diagnostic MCP9003, producing build warnings. The Params property is also changed from TParams? to TParams.

Migration: Use the new 3-arg constructor: new RequestContext<TParams>(server, request, parameters).

What's Changed

Documentation Updates

Repository Infrastructure Updates

Acknowledgements

Full Changelog: v1.1.0...release-1.2.0


API Compatibility Report

✅ All packages pass API compatibility validation against v1.0.0 baseline.

Existing suppressions (from v1.0.0 → v1.1.0):

  • ModelContextProtocol.Core: 8 CP0005 suppressions for McpClient.Completion (abstract member addition gated by [Experimental])

No new suppressions required for v1.2.0.

API Diff Report

ModelContextProtocol.Core

  namespace ModelContextProtocol.Server
  {
      public sealed class McpServerToolAttribute
      {
+         public System.Type? OutputSchemaType { get; set; }
      }
      public sealed class McpServerToolCreateOptions
      {
+         public System.Text.Json.JsonElement? OutputSchema { get; set; }
      }
      public sealed class RequestContext<TParams> : ModelContextProtocol.Server.MessageContext
      {
-         public TParams? Params { get; set; }
+         public TParams Params { get; set; }
+         public RequestContext(McpServer server, JsonRpcRequest jsonRpcRequest, TParams parameters);
      }
  }

ModelContextProtocol

No API changes.

ModelContextProtocol.AspNetCore

  namespace ModelContextProtocol.AspNetCore
  {
      public class HttpServerTransportOptions
      {
+         public bool? EnableLegacySse { get; set; }
      }
  }

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@halter73
Copy link
Copy Markdown
Contributor

halter73 commented Mar 27, 2026

I'd put exactly this breaking change first.

Migrating from legacy SSE

If your clients connect to a /sse endpoint (e.g., https://my-server.example.com/sse), they were using the legacy SSE transport — if not running in Stateless mode. The /sse and /message endpoints are now disabled by default (xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.EnableLegacySse is false and marked [Obsolete] with diagnostic MCP9003). Upgrading the server SDK without updating clients will break SSE connections.

Client-side migration. Change the client Endpoint from the /sse path to the root MCP endpoint — the same URL your server passes to MapMcp(). For example:

// Before (legacy SSE):
Endpoint = new Uri("https://my-server.example.com/sse")

// After (Streamable HTTP):
Endpoint = new Uri("https://my-server.example.com/")

With the default xref:ModelContextProtocol.Client.HttpTransportMode.AutoDetect transport mode, the client automatically tries Streamable HTTP first. You can also set TransportMode = HttpTransportMode.StreamableHttp explicitly if you know the server supports it.

Server-side migration. If you previously relied on /sse being mapped automatically, you now need EnableLegacySse = true (suppressing the MCP9003 warning) to keep serving those endpoints. The recommended path is to migrate all clients to Streamable HTTP and then remove EnableLegacySse.

Transition period. If some clients still need SSE while others have already migrated to Streamable HTTP, set EnableLegacySse = true with Stateless = false. Both transports are served simultaneously by MapMcp() — Streamable HTTP on the root endpoint and SSE on /sse and /message. Once all clients have migrated, remove EnableLegacySse and optionally switch to Stateless = true.

Then if that fails for whatever reason, then there's the obsolete option/appcontext switch

@halter73
Copy link
Copy Markdown
Contributor

halter73 commented Mar 27, 2026

Also link to the caveats.

SSE (legacy — opt-in only)

Legacy SSE endpoints are disabled by default and must be explicitly enabled via xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.EnableLegacySse. This is the primary reason they are disabled — the SSE transport has no built-in HTTP-level backpressure.

The legacy SSE transport separates the request and response channels: clients POST JSON-RPC messages to /message and receive responses through a long-lived GET SSE stream on /sse. The POST endpoint returns 202 Accepted immediately after queuing the message — it does not wait for the handler to complete. This means there is no HTTP-level backpressure on handler concurrency, because each POST frees its connection immediately regardless of how long the handler runs.

Internally, handlers are dispatched with a fire-and-forget pattern. A client can send unlimited POST requests to /message while keeping the GET stream open, and each one spawns a concurrent handler with no built-in limit.

The GET stream does provide session lifetime bounds: handler cancellation tokens are linked to the GET request's HttpContext.RequestAborted, so when the client disconnects the SSE stream, all in-flight handlers are cancelled. This is similar to SignalR's connection-bound lifetime model — but unlike SignalR, there is no per-client concurrency limit like MaximumParallelInvocationsPerClient. The GET stream provides cleanup on disconnect, not rate-limiting during the connection.

@jeffhandley jeffhandley merged commit 498de08 into main Mar 27, 2026
27 of 31 checks passed
@jeffhandley jeffhandley deleted the release-1.2.0 branch March 27, 2026 23:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants